using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using TrackMe.Apps.VM;
using Microsoft.TeamFoundation.Client;

namespace TrackMe.Apps.Integration.TFS
{
	public class TfsConnection : ITfsServer
	{
		private readonly Uri uri;
		private TfsTeamProjectCollection projectCollection;

		public TfsConnection(string uri)
		{
			this.uri = new Uri(uri);
		}

		public TfsTeamProjectCollection ProjectCollection
		{
			get
			{
				var result = projectCollection ?? (projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(uri));
				result.EnsureAuthenticated();
				return result;
			}
		}

		// WIQL
		// http://blogs.msdn.com/b/team_foundation/archive/2010/07/02/wiql-syntax-for-link-query.aspx
		//http://blogs.msdn.com/b/team_foundation/archive/2010/06/16/work-item-tracking-queries-object-model-in-2010.aspx

		private delegate T Call<out T>();

		private T Do<T>(Call<T> action)
		{
			try
			{
				return action();
			}
			catch
			{
				try
				{
					projectCollection = null;
				}
				catch
				{
					// eat exceptions on release
				}
				throw;
			}
		}


		public IList<ITfsWorkItem> WorkItems(DateTime modifiedSince)
		{
			return Do(() =>
			{
				string wiqlQuery = string.Format(
					"Select ID, Title from Issue where ([System.ChangedDate] > '{0}') and ([System.AssignedTo] = @me)",
					modifiedSince); // (State = 'Proposed')

				var context = new System.Collections.Hashtable();
				//context.Add("@project", m_projectName);
				var query = new Query(Store, wiqlQuery, context, false);

				return MapItems(query.RunQuery());
			});
		}

		private static List<ITfsWorkItem> MapItems(WorkItemCollection workItems)
		{
			return (workItems.Cast<WorkItem>().Select(MapItem)).ToList();
		}

		private static ITfsWorkItem MapItem(WorkItem w)
		{
			var item = AutoIntf.New<ITfsWorkItem>();
			item.Id = w.Id;
			item.Title = w.Title;
			item.ProjectId = w.Project.Id;
			item.ProjectName = w.Project.Name;
			for (int i = 0; i < w.WorkItemLinks.Count; i++)
			{
				var link = w.WorkItemLinks[i];
				if (link.LinkTypeEnd.Name == "Parent")
					item.ParentId = link.TargetId;
			}
			return item;
		}

		public IEnumerable<ITfsWorkItem> FetchItems(int[] issuesToFetch)
		{
			return Do(() => issuesToFetch.Select(id => MapItem(Store.GetWorkItem(id))).ToList());
		}

		private WorkItemStore Store
		{
			get
			{
				var workItemStore = ProjectCollection.GetService<WorkItemStore>();
				// if workItemStore is null, you wont get any real exception, just a NullReferenceException.
				// by newing the WorkItemStore, you will get a more descriptive exception..
				if (workItemStore == null)
				{
					var tmpTest = new WorkItemStore(ProjectCollection);
				}
				return workItemStore;
			}
		}

		public void UpdateWorkload(int issueId, TimeSpan elapsed)
		{
			Do(() => DoUpdateWorkload(issueId, elapsed.TotalHours));
		}

		bool DoUpdateWorkload(int issueId, double hrs)
		{
			const string refNameEstimate = "Microsoft.VSTS.Scheduling.OriginalEstimate";
			const string refNameRemaining = "Microsoft.VSTS.Scheduling.RemainingWork";
			const string refNameCompleted = "Microsoft.VSTS.Scheduling.CompletedWork";

			// Extract the required information out of the eventXml
			var item = Store.GetWorkItem(issueId);

			// Update the effort fields with the difference between the old and the new value
			var estimate = Convert.ToDouble(item.Fields[refNameEstimate].Value);  
			var remaining = Convert.ToDouble(item.Fields[refNameRemaining].Value);
			var completed = Convert.ToDouble(item.Fields[refNameCompleted].Value);
			item.Fields[refNameCompleted].Value = completed + hrs;
			ArrayList errors = item.Validate();

			if (errors.Count > 0)
			{
				Debug.WriteLine("There was an error adding this work item to the work item repository");
				return false;
			}
			item.Save(SaveFlags.None);
			return true;
		}

        /// <summary>
        /// Returns the parent work item of the work item with the specified id. When it has no parent, null is returned.
        /// </summary>
        public WorkItem OpenParentWorkItem(int id)
        {
            // Get the work item with the specified id
            var workItem = Store.GetWorkItem(id);
 
            // Get the link to the parent work item through the work item links
            var q = from l in workItem.WorkItemLinks.OfType<WorkItemLink>()
                    where l.LinkTypeEnd.LinkType.LinkTopology == WorkItemLinkType.Topology.Tree
                    && !l.LinkTypeEnd.IsForwardLink
                    select l.TargetId;
 
            // If there is a link with a parent work item
            if (q.Count() > 0)
            {
                // Return that one
				return Store.GetWorkItem(q.ElementAt(0));
            }
            else
            {
                return null;
            }
 
        }
	}
}